package com.gitee.wsl.mathematics.geometry.d2.curve

import com.gitee.wsl.mathematics.geometry.d2.contour.box.Box
import com.gitee.wsl.mathematics.geometry.d2.contour.box.Box2
import com.gitee.wsl.mathematics.matrix.mat3.Mat3f
import com.gitee.wsl.mathematics.matrix.mat3.ext.transform
import com.gitee.wsl.mathematics.vector.ext.dot
import com.gitee.wsl.mathematics.vector.vec2.Vec2f

data class CurveLine(
    private val ax: Float,
    private val ay: Float,
    private val bx: Float,
    private val by: Float
) : CurveShape2d {
    override fun transform(m: Mat3f) = line(start().transform(m), end().transform(m))

    override fun isFlat(epsilon: Double) = true

    override fun signedArea(): Float = (ax * by - bx * ay) / 2

    override fun length() = (end()-start()).length

    override fun reverse() = CurveLine(bx, by, ax, ay)

    override fun inflections() = DoubleArray(0)

    override fun position(t: Double) = when (t) {
        0.0 -> start()
        1.0 -> end()
        else -> Vec2f(ax + (bx - ax) * t, ay + (by - ay) * t)
    }

    override fun direction(t: Double) = Vec2f(bx - ax, by - ay)

    override fun range(tMin: Double, tMax: Double) = line(position(tMin), position(tMax))

    override fun split(t: Double): Array<CurveShape2d> {
        require(!(t <= 0 || t >= 1)) { "t must be within (0,1)" }
        val v = position(t)
        return arrayOf(line(start(), v), line(v, end()))
    }

    override fun nearestPoint(p: Vec2f): Double {
        val bSa = end()-start()
        val pSa = p-start()
        return (bSa dot pSa) / bSa.squaredLength.toDouble()
    }

    override fun endpoints(start: Vec2f, end: Vec2f) = line(start, end)

    override fun start() = Vec2f(ax, ay)

    override fun end() = Vec2f(bx, by)

    override fun subdivide(error: Double) = arrayOf(start(), end())

    override fun bounds() = Box.box(start(), end())

    /**
     * @param p a point in 2D space
     * @return the distance from this segment to the point
     */
    fun distance(p: Vec2f): Float {
        val t = nearestPoint(p)
        return when {
            t <= 0 -> (p-start()).length
            t >= 1 -> (p-end()).length
            else -> (p+(end()-start())*t).length
        }
    }

    override fun toString(): String {
        return "Line2(ax=$ax, ay=$ay, bx=$bx, by=$by)"
    }

    companion object {
        fun line(a: Vec2f, b: Vec2f): CurveLine {
            require(a != b) { "segments must have non-zero length $a $b" }
            return CurveLine(a.x, a.y, b.x, b.y)
        }

        fun line(b: Box2) = CurveLine(b.lx, b.ly, b.ux, b.uy)
    }
}